/******************************************************************************
 *
 * MantaFlow fluid solver framework
 * Copyright 2020 Sebastian Barschkis, Nils Thuerey
 *
 * This program is free software, distributed under the terms of the
 * Apache License, Version 2.0 
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Loading and writing grids and particles from and to OpenVDB files.
 *
 ******************************************************************************/

#include <iostream>
#include <fstream>
#include <cstdlib>
#include <cstring>

#include "mantaio.h"
#include "grid.h"
#include "vector4d.h"
#include "grid4d.h"
#include "particle.h"

#if OPENVDB==1
#	include "openvdb/openvdb.h"
#	include <openvdb/points/PointConversion.h>
#	include <openvdb/points/PointCount.h>
#endif

#define POSITION_NAME "P"
#define FLAG_NAME "U"

using namespace std;

namespace Manta {

#if OPENVDB==1

template<class GridType, class T>
void importVDB(typename GridType::Ptr from, Grid<T>* to) {
	using ValueT = typename GridType::ValueType;
	typename GridType::Accessor accessor = from->getAccessor();

	FOR_IJK(*to) {
		openvdb::Coord xyz(i, j, k);
		ValueT vdbValue = accessor.getValue(xyz);
		T toMantaValue;
		convertFrom(vdbValue, &toMantaValue);
		to->set(i, j, k, toMantaValue);
	}
}

template<class VDBType, class T>
void importVDB(VDBType vdbValue, ParticleDataImpl<T>* to, int index, float voxelSize) {
	(void) voxelSize; // Unused
	T toMantaValue;
	convertFrom(vdbValue, &toMantaValue);
	to->set(index, toMantaValue);
}

void importVDB(openvdb::points::PointDataGrid::Ptr from, BasicParticleSystem* to, std::vector<ParticleDataBase*>& toPData, float voxelSize) {
	openvdb::Index64 count = openvdb::points::pointCount(from->tree());
	to->resizeAll(count);

	int cnt = 0;
	for (auto leafIter = from->tree().cbeginLeaf(); leafIter; ++leafIter) {
		const openvdb::points::AttributeArray& positionArray = leafIter->constAttributeArray(POSITION_NAME);
		const openvdb::points::AttributeArray& flagArray = leafIter->constAttributeArray(FLAG_NAME);

		openvdb::points::AttributeHandle<openvdb::Vec3s> positionHandle(positionArray);
		openvdb::points::AttributeHandle<int> flagHandle(flagArray);

		// Get vdb handles to pdata objects in pdata list
		std::vector<std::tuple<int, openvdb::points::AttributeHandle<int>>> pDataHandlesInt;
		std::vector<std::tuple<int, openvdb::points::AttributeHandle<float>>> pDataHandlesReal;
		std::vector<std::tuple<int, openvdb::points::AttributeHandle<openvdb::Vec3s>>> pDataHandlesVec3;

		int pDataIndex = 0;
		for (ParticleDataBase* pdb : toPData) {
			std::string name = pdb->getName();
			const openvdb::points::AttributeArray& pDataArray = leafIter->constAttributeArray(name);

			if (pdb->getType() == ParticleDataBase::TypeInt) {
				openvdb::points::AttributeHandle<int> intHandle(pDataArray);
				std::tuple<int, openvdb::points::AttributeHandle<int>> tuple = std::make_tuple(pDataIndex, intHandle);
				pDataHandlesInt.push_back(tuple);
			}
			else if (pdb->getType() == ParticleDataBase::TypeReal) {
				openvdb::points::AttributeHandle<float> floatHandle(pDataArray);
				std::tuple<int, openvdb::points::AttributeHandle<float>> tuple = std::make_tuple(pDataIndex, floatHandle);
				pDataHandlesReal.push_back(tuple);
			}
			else if (pdb->getType() == ParticleDataBase::TypeVec3) {
				openvdb::points::AttributeHandle<openvdb::Vec3s> vec3Handle(pDataArray);
				std::tuple<int, openvdb::points::AttributeHandle<openvdb::Vec3s>> tuple = std::make_tuple(pDataIndex, vec3Handle);
				pDataHandlesVec3.push_back(tuple);
			}
			else {
				errMsg("importVDB: unknown ParticleDataBase type");
			}
			++pDataIndex;
		}

		for (auto indexIter = leafIter->beginIndexOn(); indexIter; ++indexIter) {
			// Extract the voxel-space position of the point (always between (-0.5, -0.5, -0.5) and (0.5, 0.5, 0.5)).
			openvdb::Vec3s voxelPosition = positionHandle.get(*indexIter);
			const openvdb::Vec3d xyz = indexIter.getCoord().asVec3d();
			// Compute the world-space position of the point.
			openvdb::Vec3f worldPosition = from->transform().indexToWorld(voxelPosition + xyz);
			int flag = flagHandle.get(*indexIter);

			Vec3 toMantaValue;
			convertFrom(worldPosition, &toMantaValue);
			(*to)[cnt].pos = toMantaValue;
			(*to)[cnt].pos /= voxelSize; // convert from world space to grid space
			(*to)[cnt].flag = flag;

			for (std::tuple<int, openvdb::points::AttributeHandle<int>> tuple : pDataHandlesInt) {
				int pDataIndex = std::get<0>(tuple);
				int vdbValue = std::get<1>(tuple).get(*indexIter);

				ParticleDataImpl<int>* pdi = dynamic_cast<ParticleDataImpl<int>*>(toPData[pDataIndex]);
				importVDB<int, int>(vdbValue, pdi, cnt, voxelSize);
			}
			for (std::tuple<int, openvdb::points::AttributeHandle<float>> tuple : pDataHandlesReal) {
				int pDataIndex = std::get<0>(tuple);
				float vdbValue = std::get<1>(tuple).get(*indexIter);

				ParticleDataImpl<Real>* pdi = dynamic_cast<ParticleDataImpl<Real>*>(toPData[pDataIndex]);
				importVDB<float, Real>(vdbValue, pdi, cnt, voxelSize);
			}
			for (std::tuple<int, openvdb::points::AttributeHandle<openvdb::Vec3s>> tuple : pDataHandlesVec3) {
				int pDataIndex = std::get<0>(tuple);
				openvdb::Vec3f voxelPosition = std::get<1>(tuple).get(*indexIter);

				ParticleDataImpl<Vec3>* pdi = dynamic_cast<ParticleDataImpl<Vec3>*>(toPData[pDataIndex]);
				importVDB<openvdb::Vec3s, Vec3>(voxelPosition, pdi, cnt, voxelSize);
			}
			++cnt;
		}
	}
}

template<class GridType>
static void setGridOptions(typename GridType::Ptr grid, string name, openvdb::GridClass cls, float voxelSize, bool precisionHalf) {
	grid->setTransform(openvdb::math::Transform::createLinearTransform(voxelSize));
	grid->setGridClass(cls);
	grid->setName(name);
	grid->setSaveFloatAsHalf(precisionHalf);
}

template<class T, class GridType>
typename GridType::Ptr exportVDB(Grid<T>* from) {
	using ValueT = typename GridType::ValueType;
	typename GridType::Ptr to = GridType::create();
	typename GridType::Accessor accessor = to->getAccessor();

	FOR_IJK(*from) {
		openvdb::Coord xyz(i, j, k);
		T fromMantaValue = (*from)(i, j, k);
		ValueT vdbValue;
		convertTo(&vdbValue, fromMantaValue);
		accessor.setValue(xyz, vdbValue);
	}
	return to;
}

template<class MantaType, class VDBType>
void exportVDB(ParticleDataImpl<MantaType>* from, openvdb::points::PointDataGrid::Ptr to, openvdb::tools::PointIndexGrid::Ptr pIndex, bool skipDeletedParts) {
	std::vector<VDBType> vdbValues;
	std::string name = from->getName();

	FOR_PARTS(*from) {
		// Optionally, skip exporting particles that have been marked as deleted
		BasicParticleSystem* pp = dynamic_cast<BasicParticleSystem*>(from->getParticleSys());
		if (skipDeletedParts && !pp->isActive(idx)) {
			continue;
		}
		MantaType fromMantaValue = (*from)[idx];
		VDBType vdbValue;
		convertTo(&vdbValue, fromMantaValue);
		vdbValues.push_back(vdbValue);
	}

	openvdb::NamePair attribute = openvdb::points::TypedAttributeArray<VDBType, openvdb::points::NullCodec>::attributeType();
	openvdb::points::appendAttribute(to->tree(), name, attribute);

	// Create a wrapper around the vdb values vector.
	const openvdb::points::PointAttributeVector<VDBType> wrapper(vdbValues);

	// Populate the attribute on the points
	openvdb::points::populateAttribute<openvdb::points::PointDataTree, openvdb::tools::PointIndexTree, openvdb::points::PointAttributeVector<VDBType>>(to->tree(), pIndex->tree(), name, wrapper);
}

openvdb::points::PointDataGrid::Ptr exportVDB(BasicParticleSystem* from, std::vector<ParticleDataBase*>& fromPData, bool skipDeletedParts, float voxelSize) {
	std::vector<openvdb::Vec3s> positions;
	std::vector<int> flags;

	FOR_PARTS(*from) {
		// Optionally, skip exporting particles that have been marked as deleted
		if (skipDeletedParts && !from->isActive(idx)) {
			continue;
		}
		Vector3D<float> pos = toVec3f( (*from)[idx].pos );
		pos *= voxelSize; // convert from grid space to world space
		openvdb::Vec3s posVDB(pos.x, pos.y, pos.z);
		positions.push_back(posVDB);

		int flag = (*from)[idx].flag;
		flags.push_back(flag);
	}

	const openvdb::points::PointAttributeVector<openvdb::Vec3s> positionsWrapper(positions);
	openvdb::math::Transform::Ptr transform = openvdb::math::Transform::createLinearTransform(voxelSize);

	openvdb::tools::PointIndexGrid::Ptr pointIndexGrid = openvdb::tools::createPointIndexGrid<openvdb::tools::PointIndexGrid>(positionsWrapper, *transform);

	// TODO (sebbas): Use custom codec for attributes?
	//using Codec = openvdb::points::FixedPointCodec</*1-byte=*/false, openvdb::points::UnitRange>;
	openvdb::points::PointDataGrid::Ptr to = openvdb::points::createPointDataGrid<openvdb::points::NullCodec/*Codec*/, openvdb::points::PointDataGrid>(*pointIndexGrid, positionsWrapper, *transform);

	openvdb::NamePair flagAttribute = openvdb::points::TypedAttributeArray<int, openvdb::points::NullCodec/*Codec*/>::attributeType();
	openvdb::points::appendAttribute(to->tree(), FLAG_NAME, flagAttribute);
	// Create a wrapper around the flag vector.
	openvdb::points::PointAttributeVector<int> flagWrapper(flags);
	// Populate the "flag" attribute on the points
	openvdb::points::populateAttribute<openvdb::points::PointDataTree, openvdb::tools::PointIndexTree, openvdb::points::PointAttributeVector<int>>(to->tree(), pointIndexGrid->tree(), FLAG_NAME, flagWrapper);

	// Add all already buffered pdata to this particle grid
	for (ParticleDataBase* pdb : fromPData) {
		if (pdb->getType() == ParticleDataBase::TypeInt) {
			debMsg("Writing int particle data '" << pdb->getName() << "'", 1);
			ParticleDataImpl<int>* pdi = dynamic_cast<ParticleDataImpl<int>*>(pdb);
			exportVDB<int, int>(pdi, to, pointIndexGrid, skipDeletedParts);
		}
		else if (pdb->getType() == ParticleDataBase::TypeReal) {
			debMsg("Writing real particle data '" << pdb->getName() << "'", 1);
			ParticleDataImpl<Real>* pdi = dynamic_cast<ParticleDataImpl<Real>*>(pdb);
			exportVDB<Real, float>(pdi, to, pointIndexGrid, skipDeletedParts);
		}
		else if (pdb->getType() == ParticleDataBase::TypeVec3) {
			debMsg("Writing Vec3 particle data '" << pdb->getName() << "'", 1);
			ParticleDataImpl<Vec3>* pdi = dynamic_cast<ParticleDataImpl<Vec3>*>(pdb);
			exportVDB<Vec3, openvdb::Vec3s>(pdi, to, pointIndexGrid, skipDeletedParts);
		}
		else {
			errMsg("exportVDB: unknown ParticleDataBase type");
		}
	}
	return to;
}

static void registerCustomCodecs() {
	using Codec = openvdb::points::FixedPointCodec</*1-byte=*/false, openvdb::points::UnitRange>;
	openvdb::points::TypedAttributeArray<int, Codec>::registerType();
}

int writeObjectsVDB(const string& filename, std::vector<PbClass*>* objects, float worldSize, bool skipDeletedParts, int compression, bool precisionHalf) {
	openvdb::initialize();
	openvdb::io::File file(filename);
	openvdb::GridPtrVec gridsVDB;

	// TODO (sebbas): Use custom codec for flag attribute?
	// Register codecs one, this makes sure custom attributes can be read
	//registerCustomCodecs();

	std::vector<ParticleDataBase*> pdbBuffer;

	for (std::vector<PbClass*>::iterator iter = objects->begin(); iter != objects->end(); ++iter) {
		openvdb::GridClass gClass = openvdb::GRID_UNKNOWN;
		openvdb::GridBase::Ptr vdbGrid;

		PbClass* object = dynamic_cast<PbClass*>(*iter);
		const Real dx = object->getParent()->getDx();
		const Real voxelSize = worldSize * dx;
		const string objectName = object->getName();

		if (GridBase* mantaGrid = dynamic_cast<GridBase*>(*iter)) {

			if (mantaGrid->getType() & GridBase::TypeInt) {
				debMsg("Writing int grid '" << mantaGrid->getName() << "' to vdb file " << filename, 1);
				Grid<int>* mantaIntGrid = (Grid<int>*) mantaGrid;
				vdbGrid = exportVDB<int, openvdb::Int32Grid>(mantaIntGrid);
				gridsVDB.push_back(vdbGrid);
			}
			else if (mantaGrid->getType() & GridBase::TypeReal) {
				debMsg("Writing real grid '" << mantaGrid->getName() << "' to vdb file " << filename, 1);
				gClass = (mantaGrid->getType() & GridBase::TypeLevelset) ? openvdb::GRID_LEVEL_SET : openvdb::GRID_FOG_VOLUME;
				Grid<Real>* mantaRealGrid = (Grid<Real>*) mantaGrid;
				vdbGrid = exportVDB<Real, openvdb::FloatGrid>(mantaRealGrid);
				gridsVDB.push_back(vdbGrid);
			}
			else if (mantaGrid->getType() & GridBase::TypeVec3) {
				debMsg("Writing vec3 grid '" << mantaGrid->getName() << "' to vdb file " << filename, 1);
				gClass = (mantaGrid->getType() & GridBase::TypeMAC) ? openvdb::GRID_STAGGERED : openvdb::GRID_UNKNOWN;
				Grid<Vec3>* mantaVec3Grid = (Grid<Vec3>*) mantaGrid;
				vdbGrid = exportVDB<Vec3, openvdb::Vec3SGrid>(mantaVec3Grid);
				gridsVDB.push_back(vdbGrid);
			}
			else {
				errMsg("writeObjectsVDB: unknown grid type");
				return 0;
			}
		}
		else if (BasicParticleSystem* mantaPP = dynamic_cast<BasicParticleSystem*>(*iter)) {
			debMsg("Writing particle system '" << mantaPP->getName() << "' (and buffered pData) to vdb file " << filename, 1);
			vdbGrid = exportVDB(mantaPP, pdbBuffer, skipDeletedParts, voxelSize);
			gridsVDB.push_back(vdbGrid);
			pdbBuffer.clear();

		}
		// Particle data will only be saved if there is a particle system too.
		else if (ParticleDataBase* mantaPPImpl = dynamic_cast<ParticleDataBase*>(*iter)) {
			debMsg("Buffering particle data '" << mantaPPImpl->getName() << "' to vdb file " << filename, 1);
			pdbBuffer.push_back(mantaPPImpl);

		}
		else {
			errMsg("writeObjectsVDB: Unsupported Python object. Cannot write to .vdb file " << filename);
			return 0;
		}

		// Set additional grid attributes, e.g. name, grid class, compression level, etc.
		if (vdbGrid) {
			setGridOptions<openvdb::GridBase>(vdbGrid, objectName, gClass, voxelSize, precisionHalf);
		}
	}

	// Give out a warning if pData items were present but could not be saved due to missing particle system.
	if (!pdbBuffer.empty()) {
		for (ParticleDataBase* pdb : pdbBuffer) {
			debMsg("writeObjectsVDB Warning: Particle data '" << pdb->getName() << "' has not been saved. It's parent particle system was needs to be given too.", 1);
		}
	}

	// Write only if the is at least one grid, optionally write with compression.
	if (gridsVDB.size()) {
		int vdb_flags = openvdb::io::COMPRESS_ACTIVE_MASK;
		switch (compression) {
			case COMPRESSION_NONE: {
				vdb_flags = openvdb::io::COMPRESS_NONE;
				break;
			}
			case COMPRESSION_ZIP: {
				vdb_flags |= openvdb::io::COMPRESS_ZIP;
				break;
			}
			case COMPRESSION_BLOSC: {
#if OPENVDB_BLOSC==1
				vdb_flags |= openvdb::io::COMPRESS_BLOSC;
#else
				debMsg("OpenVDB was built without Blosc support, using Zip compression instead", 1);
				vdb_flags |= openvdb::io::COMPRESS_ZIP;
#endif // OPENVDB_BLOSC==1
				break;
			}
		}
		file.setCompression(vdb_flags);
		file.write(gridsVDB);
	}
	file.close();
	return 1;
}

int readObjectsVDB(const string& filename, std::vector<PbClass*>* objects, float worldSize) {

	openvdb::initialize();
	openvdb::io::File file(filename);
	openvdb::GridPtrVec gridsVDB;

	// TODO (sebbas): Use custom codec for flag attribute?
	// Register codecs one, this makes sure custom attributes can be read
	//registerCustomCodecs();

	try {
		file.setCopyMaxBytes(0);
		file.open();
		gridsVDB = *(file.getGrids());
		openvdb::MetaMap::Ptr metadata = file.getMetadata();
		(void) metadata; // Unused for now
	}
	catch (const openvdb::IoError &e) {
		debMsg("readObjectsVDB: Could not open vdb file " << filename, 1);
		file.close();
		return 0;
	}
	file.close();

	// A buffer to store a handle to pData objects. These will be read alongside a particle system.
	std::vector<ParticleDataBase*> pdbBuffer;

	for (std::vector<PbClass*>::iterator iter = objects->begin(); iter != objects->end(); ++iter) {

		if (gridsVDB.empty()) {
			debMsg("readObjectsVDB: No vdb grids in file " << filename, 1);
		}
		// If there is just one grid in this file, load it regardless of name match (to vdb caches per grid).
		bool onlyGrid = (gridsVDB.size() == 1);

		PbClass* object = dynamic_cast<PbClass*>(*iter);
		const Real dx = object->getParent()->getDx();
		const Real voxelSize = worldSize * dx;

		// Particle data objects are treated separately - buffered and inserted when reading the particle system
		if (ParticleDataBase* mantaPPImpl = dynamic_cast<ParticleDataBase*>(*iter)) {
			debMsg("Buffering particle data '" << mantaPPImpl->getName() << "' from vdb file " << filename, 1);
			pdbBuffer.push_back(mantaPPImpl);
			continue;
		}

		// For every manta object, we loop through the vdb grid list and check for a match
		for (const openvdb::GridBase::Ptr& vdbGrid : gridsVDB) {
			bool nameMatch = (vdbGrid->getName() == (*iter)->getName());

			// Sanity checks: Only load valid grids and make sure names match.
			if (!vdbGrid) {
				debMsg("Skipping invalid vdb grid '" << vdbGrid->getName() << "' in file " << filename, 1);
				continue;
			}
			if (!nameMatch && !onlyGrid) {
				continue;
			}
			if (GridBase* mantaGrid = dynamic_cast<GridBase*>(*iter)) {
				
				if (mantaGrid->getType() & GridBase::TypeInt) {
					debMsg("Reading into grid '" << mantaGrid->getName() << "' from int grid '" << vdbGrid->getName() << "' in vdb file " << filename, 1);
					openvdb::Int32Grid::Ptr vdbIntGrid = openvdb::gridPtrCast<openvdb::Int32Grid>(vdbGrid);
					Grid<int>* mantaIntGrid = (Grid<int>*) mantaGrid;
					importVDB<openvdb::Int32Grid, int>(vdbIntGrid, mantaIntGrid);
				}
				else if (mantaGrid->getType() & GridBase::TypeReal) {
					debMsg("Reading into grid '" << mantaGrid->getName() << "' from real grid '" << vdbGrid->getName() << "' in vdb file " << filename, 1);
					openvdb::FloatGrid::Ptr vdbFloatGrid = openvdb::gridPtrCast<openvdb::FloatGrid>(vdbGrid);
					Grid<Real>* mantaRealGrid = (Grid<Real>*) mantaGrid;
					importVDB<openvdb::FloatGrid, Real>(vdbFloatGrid, mantaRealGrid);
				}
				else if (mantaGrid->getType() & GridBase::TypeVec3) {
					debMsg("Reading into grid '" << mantaGrid->getName() << "' from vec3 grid '" << vdbGrid->getName() << "' in vdb file " << filename, 1);
					openvdb::Vec3SGrid::Ptr vdbVec3Grid = openvdb::gridPtrCast<openvdb::Vec3SGrid>(vdbGrid);
					Grid<Vec3>* mantaVec3Grid = (Grid<Vec3>*) mantaGrid;
					importVDB<openvdb::Vec3SGrid, Vec3>(vdbVec3Grid, mantaVec3Grid);
				}
				else {
					errMsg("readObjectsVDB: unknown grid type");
					return 0;
				}
			}
			else if (BasicParticleSystem* mantaPP = dynamic_cast<BasicParticleSystem*>(*iter)) {
				debMsg("Reading into particle system '" << mantaPP->getName() << "' from particle system '" << vdbGrid->getName() << "' in vdb file " << filename, 1);
				openvdb::points::PointDataGrid::Ptr vdbPointGrid = openvdb::gridPtrCast<openvdb::points::PointDataGrid>(vdbGrid);
				importVDB(vdbPointGrid, mantaPP, pdbBuffer, voxelSize);
				pdbBuffer.clear();

			}
			else {
				errMsg("readObjectsVDB: Unsupported Python object. Cannot read from .vdb file " << filename);
				return 0;
			}
		}
	}

	// Give out a warning if pData items were present but could not be read due to missing particle system.
	if (!pdbBuffer.empty()) {
		for (ParticleDataBase* pdb : pdbBuffer) {
			debMsg("readObjectsVDB Warning: Particle data '" << pdb->getName() << "' has not been read. The parent particle system needs to be given too.", 1);
		}
	}

	return 1;
}

template void importVDB<int, int>(int vdbValue, ParticleDataImpl<int>* to, int index, float voxelSize=1.0);
template void importVDB<float, Real>(float vdbValue, ParticleDataImpl<Real>* to, int index, float voxelSize=1.0);
template void importVDB<openvdb::Vec3f, Vec3>(openvdb::Vec3s vdbValue, ParticleDataImpl<Vec3>* to, int index, float voxelSize=1.0);

void importVDB(openvdb::points::PointDataGrid::Ptr from, BasicParticleSystem *to, std::vector<ParticleDataBase*>& toPData, float voxelSize=1.0);
template void importVDB<openvdb::Int32Grid, int>(openvdb::Int32Grid::Ptr from, Grid<int> *to);
template void importVDB<openvdb::FloatGrid, Real>(openvdb::FloatGrid::Ptr from, Grid<Real> *to);
template void importVDB<openvdb::Vec3SGrid, Vec3>(openvdb::Vec3SGrid::Ptr from, Grid<Vec3> *to);

template openvdb::Int32Grid::Ptr exportVDB<int, openvdb::Int32Grid>(Grid<int> *from);
template openvdb::FloatGrid::Ptr exportVDB<Real, openvdb::FloatGrid>(Grid<Real> *from);
template openvdb::Vec3SGrid::Ptr exportVDB<Vec3, openvdb::Vec3SGrid>(Grid<Vec3> *from);

openvdb::points::PointDataGrid::Ptr exportVDB(BasicParticleSystem *from, std::vector<ParticleDataBase*>& fromPData, bool skipDeletedParts=false, float voxelSize=1.0);
template void exportVDB<int, int>(ParticleDataImpl<int>* from, openvdb::points::PointDataGrid::Ptr to, openvdb::tools::PointIndexGrid::Ptr pIndex, bool skipDeletedParts=false);
template void exportVDB<Real, float>(ParticleDataImpl<Real>* from, openvdb::points::PointDataGrid::Ptr to, openvdb::tools::PointIndexGrid::Ptr pIndex, bool skipDeletedParts=false);
template void exportVDB<Vec3, openvdb::Vec3s>(ParticleDataImpl<Vec3>* from, openvdb::points::PointDataGrid::Ptr to, openvdb::tools::PointIndexGrid::Ptr pIndex, bool skipDeletedParts=false);

#else

int writeObjectsVDB(const string& filename, std::vector<PbClass*>* objects, float worldSize, bool skipDeletedParts, int compression, bool precisionHalf) {
	errMsg("Cannot save to .vdb file. Mantaflow has not been built with OpenVDB support.");
	return 0;
}

int readObjectsVDB(const string& filename, std::vector<PbClass*>* objects, float worldSize) {
	errMsg("Cannot load from .vdb file. Mantaflow has not been built with OpenVDB support.");
	return 0;
}

#endif // OPENVDB==1

} //namespace
